package jxtend.processor.eclipse.visitor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jxtend.processor.eclipse.utils.EclipseProcessorUtils;
import jxtend.processor.eclipse.visitor.handler.ASTNodeVisitorContext;
import jxtend.processor.eclipse.visitor.handler.ASTNodeVisitorHandler;
import jxtend.processor.eclipse.visitor.handler.StatementTypeResolver;
import jxtend.processor.eclipse.visitor.handler.impl.ASTNodeBits;
import jxtend.processor.eclipse.visitor.handler.impl.BindingTagBits;
import jxtend.processor.eclipse.visitor.interceptor.ASTNodeVisitorInterceptor;
import jxtend.processor.eclipse.visitor.interceptor.AlternativeTraverserASTNodeVisitorInterceptor;
import jxtend.processor.eclipse.visitor.interceptor.CreateSubscopesASTNodeVisitorInterceptor;
import jxtend.processor.eclipse.visitor.interceptor.FixNullArgumentTypeOfAnonymousClassConstructor;
import jxtend.processor.eclipse.visitor.interceptor.FixParametersOfMethodBindingInterceptor;
import jxtend.processor.eclipse.visitor.interceptor.ResolveTypesForMethodBindingInterceptor;
import jxtend.processor.eclipse.visitor.interceptor.SetMissingConstantOfExpressionInterceptor;
import jxtend.processor.eclipse.visitor.replace.ASTNodeReplaceHandler;
import jxtend.processor.eclipse.visitor.traverser.ASTNodeChildrenTraverser;
import jxtend.processor.exception.JxtendProcessorException;
import jxtend.processor.reflect.ReflectField;
import jxtend.processor.reflect.ReflectMethod;
import jxtend.processor.reflect.ReflectMethod.ResultMethod2Args;
import jxtend.processor.utils.ReflectUtils;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.codeassist.CompletionEngine;
import org.eclipse.jdt.internal.codeassist.complete.CompletionParser;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Javadoc;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;

@SuppressWarnings("restriction")
public class ProcessHandlersASTNodeVisitor implements ASTNodeVisitor {
	private static final ReflectField<ReferenceBinding, SimpleLookupTable> compatibleCacheOfReferenceBinding =
		ReflectField.getField(ReferenceBinding.class, SimpleLookupTable.class, "compatibleCache");
	private static final ResultMethod2Args<Scope, Boolean, TypeParameter[], Boolean> connectTypeVariablesMethod =
		ReflectMethod.method(Scope.class, Boolean.TYPE, "connectTypeVariables", TypeParameter[].class, Boolean.TYPE);
	
	private static final ReflectField<VariableBinding, Constant> constantOfVariableBinding =
			ReflectField.getField(VariableBinding.class, Constant.class, "constant");

	private static final ReflectMethod.ResultMethod3Args<ParameterizedSingleTypeReference, TypeBinding, Scope, ReferenceBinding, Boolean>
		internalResolveTypeMethodOfParameterizedSingleTypeReference =
			ReflectMethod.method(ParameterizedSingleTypeReference.class, TypeBinding.class,
					"internalResolveType", Scope.class, ReferenceBinding.class, boolean.class);

	private final List<ASTNodeVisitorHandler> astNodeHandlers;
	final ASTNodeHandlerVisitorContextImpl context;
	private final Map<ASTNode, Integer> statementBits = new HashMap<ASTNode, Integer>();
	private final Map<ASTNode, Integer> methodBindingModifiers = new HashMap<ASTNode, Integer>();
	private final ASTVisitor undoTypeResolveOfChildrenASTVisitor;
	private final ASTVisitor undoTypeResolveOfMethodStatementASTVisitor;
	private final Set<ASTNode> ignoreEndVisits = new HashSet<ASTNode>();

	private final List<ASTNodeChildrenTraverser> astNodeChildrenTraversers;

	private final Set<ReferenceContext> referenceContextsWithIgnoreFurtherInvestigation;
	private final ASTVisitor undoTypeResolveOfCompilationUnitASTVisitor;
	private final ASTVisitor undoTypeResolveOfTypeDeclarationASTVisitor;
//	private ASTVisitor undoTypeResolveOfMethodDeclarationASTVisitor;

	public static ASTVisitor create(List<ASTNodeVisitorHandler> astNodeHandlers,
			List<ASTNodeReplaceHandler> astNodeReplaceHandlers,
			List<ASTNodeChildrenTraverser> astNodeChildrenTraversers,
			CompilationUnitDeclaration compilationUnitDeclaration,
			Set<ReferenceContext> referenceContextsWithIgnoreFurtherInvestigation) {
		ProcessHandlersASTNodeVisitor astNodeVisitor = new ProcessHandlersASTNodeVisitor(
				astNodeHandlers, astNodeReplaceHandlers, compilationUnitDeclaration, astNodeChildrenTraversers,
				referenceContextsWithIgnoreFurtherInvestigation);

		InterceptedASTNodeVisitor interceptedASTNodeVisitor = new InterceptedASTNodeVisitor(astNodeVisitor,
				astNodeVisitor.context.parentNodes,
				Arrays.<ASTNodeVisitorInterceptor>asList(
						new CreateSubscopesASTNodeVisitorInterceptor(),
						new ResolveTypesForMethodBindingInterceptor(),
//						new FixParametersOfMethodBindingInterceptor(),
//						new FixNullReturnTypeOfMethodBindingInterceptor(),
//						new FixNullArgumentTypeOfAnonymousClassConstructor(),
//						new FixNullTypeOfFieldBindingInterceptor(),
//						new SetMissingConstantOfExpressionInterceptor(),
						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)));

		ASTVisitor astVisitor = interceptedASTNodeVisitor.getASTVisitor();
		astNodeVisitor.context.setAstVisitor(astVisitor);

		return astVisitor;
	}

	private ProcessHandlersASTNodeVisitor(List<ASTNodeVisitorHandler> astNodeHandlers,
			List<ASTNodeReplaceHandler> astNodeReplaceHandlers, CompilationUnitDeclaration compilationUnitDeclaration,
			List<ASTNodeChildrenTraverser> astNodeChildrenTraversers,
			Set<ReferenceContext> referenceContextsWithIgnoreFurtherInvestigation) {
		super();

		this.astNodeHandlers = astNodeHandlers;
		this.astNodeChildrenTraversers = astNodeChildrenTraversers;
		this.referenceContextsWithIgnoreFurtherInvestigation = referenceContextsWithIgnoreFurtherInvestigation;

		List<ASTNode> parentNodes = new ArrayList<ASTNode>();
				
//		undoTypeResolveOfChildrenASTVisitor = ASTVisitorFactory.create(
//				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true), parentNodes);
		undoTypeResolveOfChildrenASTVisitor = new InterceptedASTNodeVisitor(
				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true, false, false),
				parentNodes,
				Arrays.<ASTNodeVisitorInterceptor>asList(
						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)))
				.getASTVisitor();

//		undoTypeResolveOfMethodStatementASTVisitor = ASTVisitorFactory.create(
//				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true), parentNodes);
		undoTypeResolveOfMethodStatementASTVisitor = new InterceptedASTNodeVisitor(
				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true, false, false),
				parentNodes,
				Arrays.<ASTNodeVisitorInterceptor>asList(
						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)))
				.getASTVisitor();				

//		undoTypeResolveOfMethodDeclarationASTVisitor = new InterceptedASTNodeVisitor(
//				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true, false, true),
//				parentNodes,
//				Arrays.<ASTNodeVisitorInterceptor>asList(
//						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)))
//				.getASTVisitor();

		undoTypeResolveOfCompilationUnitASTVisitor = new InterceptedASTNodeVisitor(
				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true, true, true),
				parentNodes,
				Arrays.<ASTNodeVisitorInterceptor>asList(
						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)))
				.getASTVisitor();

		undoTypeResolveOfTypeDeclarationASTVisitor = new InterceptedASTNodeVisitor(
				new UndoTypeResolveASTNodeVisitor(statementBits, methodBindingModifiers, true, false, true),
				parentNodes,
				Arrays.<ASTNodeVisitorInterceptor>asList(
						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)))
				.getASTVisitor();

		context = new ASTNodeHandlerVisitorContextImpl(parentNodes, compilationUnitDeclaration, astNodeReplaceHandlers,
				undoTypeResolveOfChildrenASTVisitor, referenceContextsWithIgnoreFurtherInvestigation);
		context.setStatementBits(statementBits);
	}

	private ASTVisitor createNewASTVisitor(CompilationUnitDeclaration compilationUnitDeclaration) {
		ProcessHandlersASTNodeVisitor astNodeVisitor = 
			new ProcessHandlersASTNodeVisitor(astNodeHandlers, context.astNodeReplaceHandlers,
					compilationUnitDeclaration, astNodeChildrenTraversers,
					referenceContextsWithIgnoreFurtherInvestigation);

		InterceptedASTNodeVisitor interceptedASTNodeVisitor = new InterceptedASTNodeVisitor(astNodeVisitor,
				context.parentNodes,
				Arrays.<ASTNodeVisitorInterceptor>asList(
						new CreateSubscopesASTNodeVisitorInterceptor(),
						new FixParametersOfMethodBindingInterceptor(),
						new FixNullArgumentTypeOfAnonymousClassConstructor(),
						new SetMissingConstantOfExpressionInterceptor(),
						new AlternativeTraverserASTNodeVisitorInterceptor(astNodeChildrenTraversers)));

		ASTVisitor astVisitor = interceptedASTNodeVisitor.getASTVisitor();
		astNodeVisitor.context.setAstVisitor(astVisitor);

		return astVisitor;
	}

	@Override
	public boolean visit(ASTNodeVisitorContext visitorContext, ASTNode node, Scope scope) {
		if ((node.bits & ASTNodeBits.JXTEND_GENERATED) != 0
				|| (node instanceof TypeDeclaration && ((TypeDeclaration) node).binding != null
						&& (((TypeDeclaration) node).binding.tagBits & BindingTagBits.ALREADY_PROCESSED) != 0)
				|| node instanceof Javadoc) {
			return false;
		}

		if ((node instanceof TypeDeclaration) && ((TypeDeclaration) node).binding != null) {
			((TypeDeclaration) node).binding.tagBits |=  BindingTagBits.ALREADY_PROCESSED;
		}
		
//		if (node instanceof CompletionOnQualifiedNameReference) {
//			Annotation annotationParent = visitorContext.findParentNode(Annotation.class);
//
//			if (annotationParent != null) {
//				int index = 1;
//
//				ASTNode currentParentNode;
//				do {
//					currentParentNode = visitorContext.getParentNode(index);
//					ignoreEndVisits.add(currentParentNode);
//
//					index++;
//				} while (currentParentNode != annotationParent);
//
//				return false;
//			}
//		}

		context.setCurrentNode(node);

		invokeBeginVisit(scope);

		ASTNode currentNode = context.getCurrentNode();

		ASTNode parentNode = context.getParentNode();

		if (node != currentNode) {
			ignoreEndVisits.add(node);

			Class<?> scopeType;
			if (context.getCurrentReferenceContext() instanceof CompilationUnitDeclaration) {
				scopeType = CompilationUnitScope.class;
			} else if (context.getCurrentReferenceContext() instanceof TypeDeclaration) {
				scopeType = ClassScope.class;
			} else {
				scopeType = BlockScope.class;
			}

			Method traverseMethod = ReflectUtils.getPublicMethod(currentNode.getClass(), "traverse",
					ASTVisitor.class, (scope instanceof MethodScope) ? BlockScope.class : scope.getClass());
			ReflectUtils.invokeMethod(traverseMethod, currentNode, context.getAstVisitor(), scope);

			return false;
		}


		//TODO: Remove 'if' (both then and else parts) and only keep the 'context.pushNode(currentNode);'
//		if (!(currentNode instanceof TypeDeclaration)
//				|| !(parentNode instanceof QualifiedAllocationExpression)) {
			context.pushNode(currentNode);
//		} else {
//			return false;
//		}

		return true;
	}

	@Override
	public void endVisit(ASTNodeVisitorContext visitorContext, ASTNode node, Scope scope) {
		//TODO: Move this to the JxtendProcessor; we need to clear all 'resolvedType' fields otherwise old values
		// will remain after running a 'updateCompilerEnvironment(LookupEnvironment, CompilationUnitDeclaration[])';
		// maybe we should introduce this call inside the 'updateCompilerEnvironment' method?
		if (node instanceof CompilationUnitDeclaration) {
			((CompilationUnitDeclaration) node).traverse(
					undoTypeResolveOfCompilationUnitASTVisitor, ((CompilationUnitDeclaration) node).scope);

			//ALREADY_PROCESSED
			return;
		}

		if ((node.bits & ASTNodeBits.JXTEND_GENERATED) != 0 || ignoreEndVisits.contains(node)
				|| node instanceof Javadoc) {
			return;
		}

		//TODO: Remove 'if'
//		if ((node instanceof TypeDeclaration)
//				&& (context.getParentNode() instanceof QualifiedAllocationExpression)) {
//			return;
//		}

		context.popNode(node);

		CompilationUnitDeclaration compilationUnitDeclaration =
				context.findParentNode(CompilationUnitDeclaration.class);
		assert compilationUnitDeclaration != null;

		if (compilationUnitDeclaration.ignoreMethodBodies) {
			return;
		}

		context.setCurrentNode(node);

		if (node instanceof TypeDeclaration) {
			TypeDeclaration typeDeclaration = (TypeDeclaration) node;
			
			if (typeDeclaration.binding != null) {
				compatibleCacheOfReferenceBinding.set(((TypeDeclaration) node).binding, null);
			}

			if (typeDeclaration.superInterfaces != null) {
				for (TypeReference typeReference : typeDeclaration.superInterfaces) {
					typeReference.resolvedType = null;
				}
			}
			if (typeDeclaration.superclass != null) {
				typeDeclaration.superclass.resolvedType = null;
			}


			//TODO: Find out why we need to initialize the 'staticInitializerScope' and 'initializerScope' when the
			//	J-xtend processor is invoked from the Eclipse JDT hook
			if (typeDeclaration.staticInitializerScope == null) {
				typeDeclaration.staticInitializerScope =
					new MethodScope(typeDeclaration.scope, typeDeclaration, true);
			}
			if (typeDeclaration.initializerScope == null) {
				typeDeclaration.initializerScope = new MethodScope(typeDeclaration.scope, typeDeclaration, false);
			}
		}

		boolean resolveType = invokeEndVisitBeforeResolve(scope);

		ASTNode currentNode = context.getCurrentNode();

		if ((currentNode instanceof MethodDeclaration || currentNode instanceof ConstructorDeclaration)
				&& ((AbstractMethodDeclaration) currentNode).scope != null) {
			int argumentsCount = ((AbstractMethodDeclaration) currentNode).arguments != null
					? ((AbstractMethodDeclaration) currentNode).arguments.length : 0;

			MethodScope methodScope = ((AbstractMethodDeclaration) currentNode).scope;

			for (int i = 0; i < methodScope.localIndex; i++) {
				methodScope.locals[i] = null;
			}
			
			methodScope.localIndex = 0;

			if ((currentNode instanceof AbstractMethodDeclaration) && (scope instanceof ClassScope)) {
				((AbstractMethodDeclaration) currentNode).traverse(
						undoTypeResolveOfChildrenASTVisitor, (ClassScope) scope);
			} else { 
				currentNode.traverse(undoTypeResolveOfChildrenASTVisitor, (BlockScope) scope);
			}
		}
		
		//TODO: make sure that all handlers works when
		//	"... || context.findParentNode(AbstractMethodDeclaration.class) != null)" to the if statement below
		if (!resolveType || !shouldResolve(scope)) {
			return;
		}

		if (currentNode instanceof TypeParameter) {
			connectTypeVariablesMethod.invoke(scope, new TypeParameter[] { (TypeParameter) currentNode }, true);
		}
		
//		//TODO: remove '|| (node instanceof QualifiedAllocationExpression)' parts of 'if' statement
//		if (scope instanceof CompilationUnitScope) { // || (node instanceof QualifiedAllocationExpression)) {
//			if (currentNode instanceof CompilationUnitDeclaration) {
//				((CompilationUnitDeclaration) currentNode).bits |= ASTNodeBits.JXTEND_GENERATED;
//			}
//
//			return;
//		}

		if ((currentNode instanceof AbstractMethodDeclaration)
				&& (((AbstractMethodDeclaration) currentNode).binding != null)
				&& ((MethodBinding) ((AbstractMethodDeclaration) currentNode).binding).declaringClass instanceof SourceTypeBinding) {
			return;
		} else if (currentNode instanceof TypeDeclaration
				&& !(context.getParentNode() instanceof AbstractMethodDeclaration)
				&& TypeDeclaration.kind(((TypeDeclaration) currentNode).modifiers) != TypeDeclaration.ENUM_DECL) { // || currentNode instanceof Annotation) {
			return;
		}

//		if ((currentNode instanceof TypeDeclaration) && ((TypeDeclaration) currentNode).binding != null) {
//			CompilationUnitDeclaration compilationUnitDeclaration =
//				((TypeDeclaration) currentNode).scope.compilationUnitScope().referenceContext;
//
//			compilationUnitDeclaration.traverse(
//					undoTypeResolveOfMethodStatementASTVisitor, compilationUnitDeclaration.scope);
//
//			EclipseProcessorUtils.updateCompilerEnvironment(context.getLookupEnvironment(),
//					new CompilationUnitDeclaration[] { compilationUnitDeclaration });
//
//			JxtendProcessor.fixParametersOfMethodBindings(compilationUnitDeclaration);
//		}
		
		boolean statementAlreadyResolved = false;

		// if the node is a statement or a local inner/anonymous type (a 'package member' and 'member inner'-type
		// will never reach this code since they use a CompilationUnitScope respectively ClassScope, instead of a
		// BlockScope)
		if (currentNode instanceof Statement) {
			// first let the handlers resolve the statement
			statementAlreadyResolved = invokeResolveStatement((Statement) currentNode, scope);
		}

			// if it was not already resolved 
			if ((!statementAlreadyResolved) && (currentNode instanceof Statement) || (currentNode instanceof AbstractMethodDeclaration)) {
				statementBits.put(currentNode, currentNode.bits);

				if ((currentNode instanceof AbstractMethodDeclaration)
						&& (((AbstractMethodDeclaration) currentNode).binding != null)) {
					methodBindingModifiers.put(
							currentNode, ((AbstractMethodDeclaration) currentNode).binding.modifiers);
				}
				
//					if (currentNode instanceof QualifiedAllocationExpression) {
//						QualifiedAllocationExpression qualifiedAllocationExpression =
//							(QualifiedAllocationExpression) currentNode;
//						ReferenceBinding superBinding =
//							(ReferenceBinding) qualifiedAllocationExpression.type.resolvedType;
//
//						// from BlockScope.addAnonymousType(TypeDeclaration anonymousType, ReferenceBinding)
//						((BlockScope) scope).addAnonymousType(
//								qualifiedAllocationExpression.anonymousType, superBinding);
//					}

				TypeBinding argumentType = null;
				if ((currentNode instanceof Argument) && (((Argument) currentNode).type != null)
						&& (((Argument) currentNode).type.resolvedType != null)
						&& (EclipseProcessorUtils.isValidTypeReference(((Argument) currentNode).type.resolvedType)
								|| ((Argument) currentNode).type.resolvedType instanceof BaseTypeBinding
								|| ((Argument) currentNode).type.resolvedType instanceof ArrayBinding)) {
					argumentType = ((Argument) currentNode).type.resolvedType;
				}
				
//				if (argumentType == null) {
//					argumentType = context.getLookupEnvironment().getResolvedType(TypeConstants.JAVA_LANG_OBJECT, scope);
//				}

				if (((currentNode instanceof Statement) && !(currentNode instanceof TypeDeclaration))
						|| (currentNode instanceof AbstractMethodDeclaration)) {
//					if ((currentNode instanceof LocalDeclaration || currentNode instanceof NameReference
//							|| currentNode instanceof TypeReference) && scope instanceof BlockScope) {
//						((Statement) currentNode).traverse(undoTypeResolveOfChildrenASTVisitor, (BlockScope) scope);
//					} else {
//						return;
//					}


					if ((currentNode instanceof AbstractMethodDeclaration) && (scope instanceof ClassScope)) {
						((AbstractMethodDeclaration) currentNode).traverse(
								undoTypeResolveOfChildrenASTVisitor, (ClassScope) scope);
					} else if ((currentNode instanceof TypeParameter) && (scope instanceof ClassScope)) {
						((TypeParameter) currentNode).traverse(undoTypeResolveOfChildrenASTVisitor, (ClassScope) scope);
					} else if ((currentNode instanceof Expression) && (scope instanceof ClassScope)) {
						((Expression) currentNode).traverse(undoTypeResolveOfChildrenASTVisitor, (ClassScope) scope);
					} else { 
						currentNode.traverse(undoTypeResolveOfChildrenASTVisitor, (BlockScope) scope);
					}
				} else if (currentNode instanceof TypeDeclaration) {
					if (scope instanceof CompilationUnitScope) {
						((TypeDeclaration) currentNode).traverse(
								undoTypeResolveOfTypeDeclarationASTVisitor, (CompilationUnitScope) scope);
					} else if (scope instanceof ClassScope) {
						((TypeDeclaration) currentNode).traverse(
								undoTypeResolveOfTypeDeclarationASTVisitor, (ClassScope) scope);
					} else {
						assert scope instanceof BlockScope;

						((TypeDeclaration) currentNode).traverse(
								undoTypeResolveOfTypeDeclarationASTVisitor, (BlockScope) scope);
					}
				}

//					Class<? extends ASTNode> astNodeClass = ASTNodeUtils.getClassForNormalKindASTNode(currentNode);
//					Field scopeField = ReflectUtils.findInaccessibleField(astNodeClass, "scope");
//
//					if (scopeField != null) {
//						Scope innerScope = (Scope) ReflectUtils.fieldValue(scopeField, currentNode);
//
//						if (!(currentNode instanceof TypeDeclaration)
//								|| !(((TypeDeclaration) currentNode).allocation
//										instanceof QualifiedAllocationExpression)) {
//							if (addedScopes.contains(innerScope)) {
//								ReflectUtils.setFieldValue(scopeField, currentNode, null);							
//							}
//
////						} else if 
////							ReflectUtils.setFieldValue(scopeField, currentNode, null);
//						}
//					} else if ((currentNode instanceof QualifiedAllocationExpression)
//							&& (((QualifiedAllocationExpression) currentNode).anonymousType != null)) {
//						TypeDeclaration anonymousType = ((QualifiedAllocationExpression) currentNode).anonymousType;
//
//						if (addedScopes.contains(anonymousType.scope)) {
//							anonymousType.scope = null;
//						}
//					}
				
				
				ASTNode parentNode = context.getParentNode();
				TypeDeclaration enclosingType = context.findParentNode(TypeDeclaration.class);
				
				if ((currentNode instanceof Annotation) && (((Annotation) currentNode).resolvedType != null)
						&& ((Annotation) currentNode).resolvedType.isValidBinding()) {
					// Do nothing
				} else if ((currentNode instanceof Argument) && ((parentNode instanceof ConstructorDeclaration)
						|| (parentNode instanceof MethodDeclaration))) {
					if (((AbstractMethodDeclaration) parentNode).scope == null) {
						//TODO: decide what to do in this situation?!?
						return;
					}

////					if (((Argument) currentNode).binding == null) {
//					
//					TypeDeclaration parentTypeDeclaration = context.findParentNode(TypeDeclaration.class);
////					parentTypeDeclaration.scope.parent instanceof ClassScope
//
//					if (parentTypeDeclaration == null
//							|| ((parentTypeDeclaration.bits & ASTNode.IsAnonymousType) == 0
//									|| (parentTypeDeclaration.scope.parent.referenceContext() instanceof AbstractMethodDeclaration)
//											|| (parentTypeDeclaration.scope.parent.referenceContext() instanceof TypeDeclaration
//													&& (((TypeDeclaration) parentTypeDeclaration.scope.parent.referenceContext()).initializerScope
//															== parentTypeDeclaration.scope.parent)))) {
////														|| ((TypeDeclaration) parentTypeDeclaration.scope.parent.referenceContext()).staticInitializerScope
////																== parentTypeDeclaration.scope.parent)))) {
//						
//						if (context.getParentNode() instanceof AbstractMethodDeclaration) {
//						if (((Argument) currentNode).binding != null && ((Argument) currentNode).binding.type == null) {
//							((Argument) currentNode).binding = null;
//						}

//						if (((Argument) currentNode).binding == null) {
						if (((Argument) currentNode).binding == null || (!(((Argument) currentNode).binding instanceof LocalVariableBinding)
							|| (scope instanceof MethodScope
									&& ((MethodScope) scope).findVariable(((Argument) currentNode).name) == null)
							|| (((Argument) currentNode).binding != null
									&& ((Argument) currentNode).binding.getClass().getName().equals(
											"org.eclipse.jdt.internal.compiler.lookup.AptSourceLocalVariableBinding")))) {
//						if (((Argument) currentNode).binding == null //|| (!(((Argument) currentNode).binding instanceof LocalVariableBinding)
//								|| (scope instanceof BlockScope
//										&& ((MethodScope) scope).findVariable(((Argument) currentNode).name) == null)) {
////								|| ((Argument) currentNode).binding instanceof AptSourceLocalVariableBinding)) {
						((Argument) currentNode).bind(
								((AbstractMethodDeclaration) parentNode).scope, argumentType, true);
						}
//						}
////						}
//					}
				} else if (currentNode instanceof AbstractMethodDeclaration) {
					((AbstractMethodDeclaration) currentNode).resolve((ClassScope) scope);
				} else if ((currentNode instanceof TypeParameter) && (scope instanceof ClassScope)) {
					((TypeParameter) currentNode).traverse(undoTypeResolveOfChildrenASTVisitor, (ClassScope) scope);
				} else if (currentNode instanceof SingleTypeReference && scope instanceof BlockScope) {
					if (enclosingType != null && enclosingType.binding != null
							&& parentNode instanceof TypeDeclaration
							&& isSuperclassOrSuperInterfaceTypeReference((TypeDeclaration) parentNode, currentNode)) {
						((SingleTypeReference) currentNode).resolveTypeEnclosing(
								(BlockScope) scope, enclosingType.binding);
					} else {
						ReferenceBinding enclosingTypeOfQualifiedAllocationExpression = null;
						if (parentNode instanceof QualifiedAllocationExpression
								&& ((QualifiedAllocationExpression) parentNode).enclosingInstance != null) {
							if (((QualifiedAllocationExpression) parentNode).enclosingInstance instanceof AllocationExpression
									&& ((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type != null
									&& ((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type.resolvedType != null
									&& EclipseProcessorUtils.isValidTypeReference(((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type.resolvedType)) {
								enclosingTypeOfQualifiedAllocationExpression = (ReferenceBinding) ((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type.resolvedType;
							} else if (((QualifiedAllocationExpression) parentNode).enclosingInstance.resolvedType != null
									&& EclipseProcessorUtils.isValidTypeReference(((QualifiedAllocationExpression) parentNode).enclosingInstance.resolvedType)) {
								enclosingTypeOfQualifiedAllocationExpression = (ReferenceBinding) ((QualifiedAllocationExpression) parentNode).enclosingInstance.resolvedType;
							}

//								&& ((QualifiedAllocationExpression) parentNode).type != null
//								&& EclipseProcessorUtils.isValidTypeReference(((QualifiedAllocationExpression) parentNode).enclosingInstance.resolvedType)) {
//										instanceof AllocationExpression
//								&& ((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type != null
//								&& ((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type.resolvedType != null
//								&& EclipseProcessorUtils.isValidTypeReference(((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type.resolvedType)) {
//									(ReferenceBinding) (((AllocationExpression) ((QualifiedAllocationExpression) parentNode).enclosingInstance).type.resolvedType);
						}

						if (currentNode instanceof ParameterizedSingleTypeReference) {
							internalResolveTypeMethodOfParameterizedSingleTypeReference.invoke(
									(ParameterizedSingleTypeReference) currentNode, scope,
									enclosingTypeOfQualifiedAllocationExpression, false);
						} else if (enclosingTypeOfQualifiedAllocationExpression != null) {
							((SingleTypeReference) currentNode).resolveTypeEnclosing(
									(BlockScope) scope, enclosingTypeOfQualifiedAllocationExpression);
						} else {
							((SingleTypeReference) currentNode).resolveType((BlockScope) scope);
						}
//						private TypeBinding internalResolveType(Scope scope, ReferenceBinding enclosingType, boolean checkBounds) {
					}
				} else if ((currentNode instanceof TypeDeclaration)
						&& ((scope instanceof ClassScope) || (scope instanceof CompilationUnitScope))) {
					ReflectMethod.voidMethod(SourceTypeBinding.class, "verifyMethods", MethodVerifier.class)
						.invoke(((TypeDeclaration) currentNode).binding, scope.environment().methodVerifier());

					if (scope instanceof ClassScope) {
//						ReflectMethod.voidMethod(SourceTypeBinding.class, "verifyMethods", MethodVerifier.class)
//							.invoke(((TypeDeclaration) currentNode).binding, scope.environment().methodVerifier());
						((TypeDeclaration) currentNode).resolve((ClassScope) scope);

////						void verifyMethods(MethodVerifier verifier)					
//						((ClassScope) scope).verifyMethods(scope.environment().methodVerifier());
//
//						((TypeDeclaration) currentNode).binding.
						
					} else {
						assert scope instanceof CompilationUnitScope;

						((TypeDeclaration) currentNode).resolve((CompilationUnitScope) scope);
					}
				} else if ((currentNode instanceof Expression) && (scope instanceof ClassScope)) {
					((Expression) currentNode).resolveType((ClassScope) scope);
				} else {
//					if (scope instanceof ClassScope) {
//						System.out.println("!!! scope instanceof ClassScope: " + currentNode.getClass().getName());
//					}
					
					if (currentNode instanceof TryStatement && scope instanceof MethodScope
							&& ((MethodScope) scope).referenceContext instanceof AbstractMethodDeclaration) {
						AbstractMethodDeclaration abstractMethodDeclaration =
								(AbstractMethodDeclaration) ((MethodScope) scope).referenceContext;
						MethodBinding methodBinding =
								((AbstractMethodDeclaration) ((MethodScope) scope).referenceContext).binding;
						if (methodBinding != null && methodBinding.returnType == null) {
//								&& abstractMethodDeclaration instanceof MethodDeclaration
//								&& ((MethodDeclaration) abstractMethodDeclaration).returnType != null
//								&& !((MethodDeclaration) abstractMethodDeclaration).returnType.toString().equals("void")) {
//							System.out.println("!!! " + ((AbstractMethodDeclaration) ((MethodScope) scope).referenceContext));
						}
					}
					
					FieldDeclaration fieldDeclarationForWhichToUndoTypeResolve = null;
					
					if (currentNode instanceof SingleNameReference) {
						Binding binding = scope.getBinding(((SingleNameReference) currentNode).token,
								currentNode.bits & ASTNode.RestrictiveFlagMASK, (SingleNameReference) currentNode,
								true);

						if (binding instanceof FieldBinding
								&& ((currentNode.bits & ASTNode.RestrictiveFlagMASK) == (Binding.VARIABLE)
									|| (currentNode.bits & ASTNode.RestrictiveFlagMASK)
											== (Binding.VARIABLE | Binding.TYPE))) {
							FieldBinding fieldBinding = (FieldBinding) binding;

							fieldDeclarationForWhichToUndoTypeResolve =
									preparePotentialFieldDeclarationResolve(scope, fieldBinding);
						}
					} else if (currentNode instanceof QualifiedNameReference && scope instanceof BlockScope)  {
						QualifiedNameReference qualifiedNameReference = (QualifiedNameReference) currentNode;

						Binding binding = ((BlockScope) scope).getBinding((qualifiedNameReference).tokens,
								currentNode.bits & ASTNode.RestrictiveFlagMASK, qualifiedNameReference, true);
						
						int maskedBits = currentNode.bits & ASTNode.RestrictiveFlagMASK;
						if (binding.isValidBinding()
								&& binding instanceof LocalVariableBinding
								&& (maskedBits == Binding.VARIABLE
									|| maskedBits == (Binding.TYPE | Binding.VARIABLE))) {
							LocalVariableBinding localVariableBinding = (LocalVariableBinding) binding;
							if (localVariableBinding.type == null
									|| (localVariableBinding.type.tagBits & TagBits.HasMissingType) == 0) {
								int length = qualifiedNameReference.tokens.length;

//								FieldBinding field = ((qualifiedNameReference.bits & Binding.FIELD) != 0)
//										? (FieldBinding) binding : null;
//								TypeBinding type = ((VariableBinding) binding).type;

								int index = qualifiedNameReference.indexOfFirstFieldBinding;
								if (qualifiedNameReference.indexOfFirstFieldBinding < qualifiedNameReference.tokens.length) {
								char[] fieldName =
										qualifiedNameReference.tokens[qualifiedNameReference.indexOfFirstFieldBinding];
								
								if (localVariableBinding.type instanceof SourceTypeBinding) {
									FieldDeclaration[] fields =
												((SourceTypeBinding) localVariableBinding.type).scope.referenceContext.fields;
									if (fields != null) {
									for (FieldDeclaration fieldDeclaration : fields) {
										if (CharOperation.equals(fieldName, fieldDeclaration.name)) {
											fieldDeclarationForWhichToUndoTypeResolve = fieldDeclaration;
											break;
										}
									}
									}
								}


								if (index == length) { //	restrictiveFlag == FIELD
									//TODO: do we really need support for "LocalVariableBinding";
									//	if so than first next 2 statements for it
//									FieldBinding fieldBinding = (FieldBinding) binding;
//
//									fieldDeclarationForWhichToUndoTypeResolve =
//											preparePotentialFieldDeclarationResolve(scope, fieldBinding);
								}
								}
							}
						} else 	if (binding.isValidBinding()
								&& binding instanceof FieldBinding
								&& (maskedBits == Binding.VARIABLE
									|| maskedBits == (Binding.TYPE | Binding.VARIABLE))) {
							int length = qualifiedNameReference.tokens.length;
//							FieldBinding field = ((qualifiedNameReference.bits & Binding.FIELD) != 0)
//									? (FieldBinding) qualifiedNameReference.binding : null;
//							TypeBinding type = ((VariableBinding) qualifiedNameReference.binding).type;
							int index = qualifiedNameReference.indexOfFirstFieldBinding;
							if (index == length) { //	restrictiveFlag == FIELD
								FieldBinding fieldBinding = (FieldBinding) binding;

								fieldDeclarationForWhichToUndoTypeResolve =
										preparePotentialFieldDeclarationResolve(scope, fieldBinding);
							}
						}
					}

//						--> scope.getBinding(this.token, this.bits & ASTNode.RestrictiveFlagMASK, this, true /*resolve*/)
//						    Indien FieldBinding, en
//							((this.bits & ASTNode.RestrictiveFlagMASK) == (Binding.VARIABLE)
//								&& (this.bits & ASTNode.RestrictiveFlagMASK) == (Binding.VARIABLE | Binding.TYPE))
//								&& (!(this.binding instanceof LocalVariableBinding)) {
//
//							if (isFinal) { -->
//
//							FieldBinding originalField = original();
//							if (originalField.declaringClass instanceof SourceTypeBinding) {
//								SourceTypeBinding sourceType = (SourceTypeBinding) originalField.declaringClass;
//								if (sourceType.scope != null) {
//									TypeDeclaration typeDecl = sourceType.scope.referenceContext;
//									FieldDeclaration fieldDecl = typeDecl.declarationOf(originalField);

					if (fieldDeclarationForWhichToUndoTypeResolve != null) {
						ReferenceBinding declaringClass =
								fieldDeclarationForWhichToUndoTypeResolve.binding.declaringClass;
						if (declaringClass instanceof SourceTypeBinding) {
							TypeDeclaration typeDeclaration =
									((SourceTypeBinding) declaringClass).scope.referenceContext;
							MethodScope fieldScope = fieldDeclarationForWhichToUndoTypeResolve.isStatic()
									? typeDeclaration.staticInitializerScope
									: typeDeclaration.initializerScope;

							fieldDeclarationForWhichToUndoTypeResolve.traverse(
									undoTypeResolveOfChildrenASTVisitor, fieldScope);
						}
					}
					
					BlockScope earlierCreatedBlockScope = null;
					if (currentNode instanceof Block) {
						earlierCreatedBlockScope = ((Block) currentNode).scope;
					}


					//getMethods(TypeConstants.INIT)
//					if (currentNode instanceof ExplicitConstructorCall && scope instanceof MethodScope
//							&& ((MethodScope) scope).parent instanceof ClassScope 
//							&& ((ClassScope) ((MethodScope) scope).parent).referenceContext.superclass != null
//							&& ((ClassScope) ((MethodScope) scope).parent).referenceContext.superclass.resolvedType
//									instanceof ReferenceBinding) {
//						((ReferenceBinding) ((ClassScope) ((MethodScope) scope).parent).referenceContext.superclass.resolvedType).getMethods(TypeConstants.INIT);
////						((ReferenceBinding) ((ClassScope) ((MethodScope) scope).parent).referenceContext.superclass)
////							.getMethods(TypeConstants.INIT);
//					}

					Expression originalInitialization = null;
					if (currentNode instanceof LocalDeclaration) {
						originalInitialization = ((LocalDeclaration) currentNode).initialization;
						((LocalDeclaration) currentNode).initialization = null;

						((LocalDeclaration) currentNode).traverse(
								undoTypeResolveOfTypeDeclarationASTVisitor, (BlockScope) scope);
					}

					try {
						if (!(currentNode instanceof Argument) || (!(((Argument) currentNode).binding instanceof LocalVariableBinding)
								|| (scope instanceof MethodScope
										&& ((MethodScope) scope).findVariable(((Argument) currentNode).name) == null)
								|| (((Argument) currentNode).binding != null
										&& ((Argument) currentNode).binding.getClass().getName().equals(
												"org.eclipse.jdt.internal.compiler.lookup.AptSourceLocalVariableBinding")))) {
							((Statement) currentNode).resolve((BlockScope) scope);

							if (currentNode instanceof NameReference
									&& ((NameReference) currentNode).resolvedType != null
									&& EclipseProcessorUtils.isValidTypeReference(
											((NameReference) currentNode).resolvedType)
									&& ((NameReference) currentNode).actualReceiverType != null
									&& ((NameReference) currentNode).actualReceiverType.isAnonymousType()
									&& ((ReferenceBinding) ((NameReference) currentNode).actualReceiverType).superclass() == ((NameReference) currentNode).resolvedType
									&& ((LocalTypeBinding) ((NameReference) currentNode).actualReceiverType).scope != null
									&& ((LocalTypeBinding) ((NameReference) currentNode).actualReceiverType)
											.scope.referenceContext != null) {
								((LocalTypeBinding) ((NameReference) currentNode).actualReceiverType).scope.referenceContext
									.traverse(undoTypeResolveOfTypeDeclarationASTVisitor,
											((LocalTypeBinding) ((NameReference) currentNode).actualReceiverType).scope);
							}
						}

					} finally {
						if (currentNode instanceof LocalDeclaration) {
							((LocalDeclaration) currentNode).initialization = originalInitialization;
						}
					}

					if (earlierCreatedBlockScope != null) {
						((Block) currentNode).scope = earlierCreatedBlockScope;
					}

					if (fieldDeclarationForWhichToUndoTypeResolve != null) {
						ReferenceBinding declaringClass =
								fieldDeclarationForWhichToUndoTypeResolve.binding.declaringClass;
						if (declaringClass instanceof SourceTypeBinding) {
							TypeDeclaration typeDeclaration =
									((SourceTypeBinding) declaringClass).scope.referenceContext;

							MethodScope fieldScope = fieldDeclarationForWhichToUndoTypeResolve.isStatic()
									? typeDeclaration.staticInitializerScope
									: typeDeclaration.initializerScope;

							fieldDeclarationForWhichToUndoTypeResolve.traverse(
								undoTypeResolveOfChildrenASTVisitor, fieldScope);
						}
					}
//					if (currentNode instanceof QualifiedAllocationExpression) {
//						AbstractMethodDeclaration[] anonymousTypeMethods =
//								((QualifiedAllocationExpression) currentNode).anonymousType.methods;
//						if (anonymousTypeMethods != null && anonymousTypeMethods.length != 0
//								&& anonymousTypeMethods[0] instanceof ConstructorDeclaration
//								&& (anonymousTypeMethods[0].bits & ASTNode.IsDefaultConstructor) != 0) {
//							anonymousTypeMethods[0].selector = new char[0];
//						}
//					} else if (currentNode instanceof SingleNameReference) {
//						((SingleNameReference) currentNode).actualReceiverType = null;
//					}
				}
			}

//			if ((currentNode instanceof NameReference) && ((NameReference) currentNode).binding != null
//					&& (Thread.currentThread().getName().equals("Text Viewer Hover Presenter")
////							|| Thread.currentThread().getName().equals("main")
//							|| Thread.currentThread().getName().equals("org.eclipse.jdt.internal.ui.text.JavaReconciler"))
//							|| currentNode.getClass().getPackage().getName().equals(
//									"org.eclipse.jdt.internal.codeassist.complete"))) {
//					&& currentNode.getClass().getPackage().getName().equals(
//							"org.eclipse.jdt.internal.codeassist.complete")) {

			
//			if (currentNode instanceof Expression && !(currentNode instanceof ThisReference)
//					&& !(currentNode instanceof AllocationExpression)
//					&& ((Expression) currentNode).resolvedType != null
//					&& ((Expression) currentNode).resolvedType instanceof SourceTypeBinding
////					currentNode instanceof NameReference && ((NameReference) currentNode).binding != null
//					&& (Thread.currentThread().getName().equals("org.eclipse.jdt.internal.ui.text.JavaReconciler")
//							|| Thread.currentThread().getName().equals("Text Viewer Hover Presenter")
//							|| currentNode.getClass().getPackage().getName().equals(
//									"org.eclipse.jdt.internal.codeassist.complete")
//							|| Thread.currentThread().getName().equals("main"))) {
//
//
////					&& currentNode.getClass().getPackage().getName().equals(
////							"org.eclipse.jdt.internal.codeassist.complete")) {
//
////				NameReference nameReference = (NameReference) currentNode;
//
//				SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) ((Expression) currentNode).resolvedType; //null;
////				if (nameReference.binding instanceof SourceTypeBinding) {
////					sourceTypeBinding = (SourceTypeBinding) nameReference.binding;
////				} else if ((nameReference.binding instanceof VariableBinding)
////						&& (((VariableBinding) nameReference.binding).type instanceof SourceTypeBinding)) {
////					sourceTypeBinding = (SourceTypeBinding) ((VariableBinding) nameReference.binding).type;
////				}
//
//
//				if (sourceTypeBinding != null && sourceTypeBinding.scope != null
////						&& sourceTypeBinding.scope.parent instanceof ClassScope
//						&& (sourceTypeBinding.tagBits & BindingTagBits.ALREADY_PROCESSED) == 0) {
//
////					if (sourceTypeBinding.scope.referenceCompilationUnit() == scope.referenceCompilationUnit() //) {
////							&& sourceTypeBinding.scope.parent instanceof ClassScope) {
////						sourceTypeBinding.scope.referenceCompilationUnit().traverse(
////								createNewASTVisitor(sourceTypeBinding.scope.referenceCompilationUnit()),
////								sourceTypeBinding.scope.referenceCompilationUnit().scope);		
////
//////						EclipseProcessorUtils.updateCompilerEnvironment(context.getLookupEnvironment(),
//////								new CompilationUnitDeclaration[] { sourceTypeBinding.scope.referenceCompilationUnit() });
////
////						EclipseProcessorUtils.updateTypeDeclaration(
////								scope.referenceCompilationUnit(), sourceTypeBinding.scope.referenceContext);
//////						System.out.println();
////					} else
//					
////					String packageName = currentNode.getClass().getPackage().getName(); 
////					if ((packageName.equals("org.eclipse.jdt.internal.codeassist.complete")
////							|| packageName.equals("org.eclipse.jdt.internal.codeassist.select"))
////							&&
//					if ((sourceTypeBinding != null) && (sourceTypeBinding.scope != null)
//							&& (sourceTypeBinding.tagBits & BindingTagBits.ALREADY_PROCESSED) == 0) {
//						List<ASTNode> parentNodes = context.getParentNodes();
//
//						boolean recursiveCall = false;
//						for (ASTNode parentNode : parentNodes) {
//							if (parentNode instanceof TypeDeclaration
//									 && ((TypeDeclaration) parentNode).binding == sourceTypeBinding) {
//								recursiveCall = true;
//							}
//						}
//
//						if (!recursiveCall) {
//							System.out.println("### sourceTypeBinding: " + sourceTypeBinding.debugName());
//							sourceTypeBinding.scope.referenceCompilationUnit().traverse(
//									createNewASTVisitor(sourceTypeBinding.scope.referenceCompilationUnit()),
//									sourceTypeBinding.scope.referenceCompilationUnit().scope);
//						}
////						Text Viewer Hover Presenter
//						
//						
////						context.getLookupEnvironment().
////						baseProcessingEnv.getCompiler().parser.getMethodBodies(
////								sourceTypeBinding.scope.referenceCompilationUnit());
//
////						EclipseProcessorUtils.updateCompilerEnvironment(context.getLookupEnvironment(),
////								new CompilationUnitDeclaration[] { sourceTypeBinding.scope.referenceCompilationUnit() });	
//					}
//				}
//			}

		if (currentNode instanceof ArrayInitializer) {
			if ((((ArrayInitializer) currentNode).binding == null)
					|| !((ArrayInitializer) currentNode).binding.isValidBinding()) {
				return;
			}
		} else if ((currentNode instanceof Expression) && ((((Expression) currentNode).resolvedType == null)
				|| !((Expression) currentNode).resolvedType.isValidBinding())) {
//				if ((((Expression) currentNode).resolvedType == null)
//						&& (currentNode instanceof CompletionOnSingleTypeReference)) {
//					CompletionOnSingleTypeReference completionOnSingleTypeReference =
//						(CompletionOnSingleTypeReference) currentNode;
//
//					((Expression) currentNode).resolvedType = new ProblemReferenceBinding(
//							new char[][] { completionOnSingleTypeReference.token }, null, ProblemReasons.NotFound);
// 				}
			
			return;
		}

		@SuppressWarnings("unchecked") ReflectField<ASTNode, TypeBinding> actualReceiverTypeOfASTNode =
			(ReflectField<ASTNode, TypeBinding>) ReflectField.findPublicField(
					node.getClass(), TypeBinding.class, "actualReceiverType");

		if (actualReceiverTypeOfASTNode != null) {
//				&& (!(node instanceof NameReference) || (node.bits & ASTNode.DepthSHIFT) != 0)) {
//				&& !(actualReceiverTypeOfASTNode.get(node) instanceof LocalTypeBinding)) {
			actualReceiverTypeOfASTNode.set(node, null);
		}

		invokeEndVisitAfterResolve(scope);

//		if ((currentNode instanceof TypeDeclaration) && ((TypeDeclaration) currentNode).binding != null) {
////			EclipseProcessorUtils.updateCompilerEnvironment(context.getLookupEnvironment(),
////					new CompilationUnitDeclaration[] {
////						((TypeDeclaration) currentNode).scope.compilationUnitScope().referenceContext });
////			EclipseProcessorUtils.updateTypeDeclaration(
////					scope.referenceCompilationUnit(), (TypeDeclaration) currentNode);
//
//			((TypeDeclaration) currentNode).binding.tagBits |=  BindingTagBits.ALREADY_PROCESSED;
//		}

//			if (currentNode instanceof Annotation) {
//				((Annotation) currentNode).recipient = null;
//				
//			//TODO: remove
////			} else if (currentNode instanceof AbstractMethodDeclaration) {
////				((AbstractMethodDeclaration) currentNode).binding.tagBits &= ~TagBits.AnnotationResolved;
//			}

		//TODO: remove
//			currentNode.traverse(clearAnnotationRecipientAndResolvedBitASTVisitor, (BlockScope) scope);
		
		//TODO: remove
//			if (node instanceof AbstractMethodDeclaration) {
//				((AbstractMethodDeclaration) node).binding.tagBits &= ~TagBits.AnnotationResolved;
//			}

		if ((currentNode instanceof Statement) && !(currentNode instanceof TypeDeclaration)
				&& !(currentNode instanceof Annotation)
				&& (context.getParentNode() instanceof AbstractMethodDeclaration)) {
			currentNode.traverse(undoTypeResolveOfMethodStatementASTVisitor, (BlockScope) scope);

//			} else if ((currentNode instanceof Annotation) 
//					&& (context.getParentNode() instanceof AbstractMethodDeclaration)) {
////					&& (((Annotation) currentNode).recipient != null)) {
//				AbstractMethodDeclaration abstractMethodDeclaration =
//					(AbstractMethodDeclaration) context.getParentNode();
//				System.out.println("!!! has AnnotationResolved bit: "
//						+ ((abstractMethodDeclaration.binding.tagBits & TagBits.AnnotationResolved) != 0));
//
//				abstractMethodDeclaration.binding.tagBits &= ~TagBits.AnnotationResolved;
//				
////				Binding annotationRecipient = ((Annotation) node).recipient;
////				Field tagBitsField = ReflectUtils.findPublicField(annotationRecipient.getClass(), "tagBits");
////				long tagBits = (Long) ReflectUtils.fieldValue(tagBitsField, annotationRecipient);
////				
////				tagBits &= ~TagBits.AnnotationResolved;
////				
////				ReflectUtils.setFieldValue(tagBitsField, annotationRecipient, tagBits);
		}
	}

	private boolean shouldResolve(Scope scope) {
		for (ASTNodeVisitorHandler handler : astNodeHandlers) {
			boolean shouldResolve = handler.shouldResolve(context, context.getCurrentNode(), scope);

			if (shouldResolve) {
				return true;
			}
		}

		return false;
	}

	private FieldDeclaration preparePotentialFieldDeclarationResolve(Scope scope, FieldBinding fieldBinding) {
		FieldDeclaration result = null;
		
		Constant constant = constantOfVariableBinding.get(fieldBinding);
		if (constant == null && fieldBinding.isFinal()) {
			FieldBinding originalField = fieldBinding.original();
			if (originalField.declaringClass instanceof SourceTypeBinding) {
				SourceTypeBinding sourceType = (SourceTypeBinding) originalField.declaringClass;
				if (sourceType.scope != null) {
					TypeDeclaration typeDecl = sourceType.scope.referenceContext;
					FieldDeclaration fieldDecl = typeDecl.declarationOf(originalField);
					
					if (fieldDecl == null) {
						return null;
					}

					result = fieldDecl;
					statementBits.put(fieldDecl, fieldDecl.bits);

					if (fieldDecl.initialization != null) {
						if (scope instanceof ClassScope) {
							fieldDecl.initialization.traverse(
								undoTypeResolveOfChildrenASTVisitor, (ClassScope) scope);
						} else {
							assert scope instanceof BlockScope;

							fieldDecl.initialization.traverse(
									undoTypeResolveOfChildrenASTVisitor, (BlockScope) scope);
						}
					}
				}
			}
		}

		return result;
	}

	private boolean isSuperclassOrSuperInterfaceTypeReference(TypeDeclaration typeDeclaration, ASTNode currentNode) {
		if (typeDeclaration.superclass == currentNode)
			return true;
		if (typeDeclaration.superInterfaces != null) {
			for (int i = 0; i < typeDeclaration.superInterfaces.length; i++) {
				if (typeDeclaration.superInterfaces[i] == currentNode) {
					return true;
				}
			}
		}

		return false;
	}

	private boolean invokeResolveStatement(Statement statement, Scope scope) {
		boolean alreadyResolved = false;
		for (ASTNodeVisitorHandler handler : astNodeHandlers) {
			if (!(handler instanceof StatementTypeResolver)) { continue; };

			boolean resolved = ((StatementTypeResolver) handler).resolveStatement(context, statement, scope);

			if (resolved) {
				if (alreadyResolved) {
					throw new JxtendProcessorException("Another StatementTypeResolver already traversed the " +
							"children of the node: %s", statement);
				}

				alreadyResolved = true;
			}
		}

		return alreadyResolved;
	}

	private void invokeBeginVisit(Scope scope) {
		ASTNode node = context.getCurrentNode();
		for (ASTNodeVisitorHandler handler : astNodeHandlers) {
			handler.beginVisit(context, node, scope);

			if (node != context.getCurrentNode()) {
				return;
			}
		}
	}

	private boolean invokeEndVisitBeforeResolve(Scope scope) {
		for (ASTNodeVisitorHandler handler : astNodeHandlers) {
			boolean resolveType = handler.endVisitBeforeResolve(context, context.getCurrentNode(), scope);

			if (!resolveType) {
				return false;
			}
		}

		return true;		
	}

	private void invokeEndVisitAfterResolve(Scope scope) {
		for (ASTNodeVisitorHandler handler : astNodeHandlers) {
			handler.endVisitAfterResolve(context, context.getCurrentNode(), scope);
		}
	}
}